package com.liuweijiaoyu.demo.widgets.oxny;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


import com.onyx.android.sdk.api.device.epd.EpdController;
import com.onyx.android.sdk.api.device.epd.UpdateMode;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class OnyxPaperView extends View implements DraftPaper {

    private final int STATUS_NONE = 0;
    private final int STATUS_ERASER = 1;
    private final int STATUS_PEN = 2;

    private static final int ERASER_WIDTH = UIUtils.dip2px(18);

    private float strokeWidth = 2;
    private final int MSG_RE_ENTER_SCRIBE = 1;
    private List<DrawPoint> mDrawPath;
    private Stack<List<DrawPoint>> mLayerIfs = new Stack<>();
    private Paint mPaint;
    //临时变量，绘制路径使用
    private Path mPath = new Path();
    volatile boolean scribbleMode = false;
    private Handler mHandler;
    private int mStatus = STATUS_NONE;
    private RectF mEraserRect = new RectF();

    private boolean mEraserEnable = true;

    public OnyxPaperView(Context context) {
        super(context);
        init();
    }

    public OnyxPaperView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public OnyxPaperView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
//        Device.currentDevice().setStrokeWidth(strokeWidth);
//        Device.currentDevice().setStrokeColor(Color.BLACK);
//        Device.currentDevice().setStrokeStyle(0); //0：pen style, 1：brush style
//        Device.currentDevice().setPainterStyle(true,   // antiAlias or not
//                Paint.Style.STROKE,         // stroke style
//                Paint.Join.ROUND,                    // join style
//                Paint.Cap.ROUND);                    // cap style
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);// 空心
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(strokeWidth);
        mPaint.setPathEffect(new CornerPathEffect(80));
        mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case MSG_RE_ENTER_SCRIBE: {
                        if (scribbleMode) {
                            EpdController.enterScribbleMode(OnyxPaperView.this);
                        }
                    }
                }
            }
        };
    }

    @Override
    public void enterScribbleMode() {
        EpdController.enterScribbleMode(this);
        scribbleMode = true;
    }

    @Override
    public void leaveScribbleMode() {
        scribbleMode = false;
        EpdController.leaveScribbleMode(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!scribbleMode) {
            return false;
        }
        int action = event.getAction();
        final float x = event.getX();
        final float y = event.getY();
        final float pressure = 1;
        final float size = 0;
        final float eventTime = 0;

        switch (action) {
            case MotionEvent.ACTION_DOWN:
            {
                int toolType = event.getToolType(0);
                if (toolType == MotionEvent.TOOL_TYPE_ERASER && mEraserEnable) {
                    this.mStatus = STATUS_ERASER;
                    //开启橡皮擦 则退出手写模式
                    EpdController.leaveScribbleMode(this);
//                    onEraserMoving(x, y);
                    eraserLine(x, y);
                } else {
                    this.mStatus = STATUS_PEN;
                    float dst[] = mapPoint(x, y);
                    mDrawPath = new ArrayList<>();
                    mLayerIfs.add(mDrawPath);
                    mDrawPath.add(new DrawPoint((int)x, (int)y));
                    EpdController.startStroke(strokeWidth, dst[0], dst[1], pressure, size, eventTime);
                }
                break;
            }
            case MotionEvent.ACTION_MOVE:
            {
                if (mStatus == STATUS_PEN) {
                    int n = event.getHistorySize();
                    for (int i = 0; i < n; i++) {
                        float dst[] = mapPoint((int) event.getHistoricalX(i), (int) event.getHistoricalY(i));
                        EpdController.addStrokePoint(strokeWidth, dst[0], dst[1], pressure, size, eventTime);
                    }
                    mDrawPath.add(new DrawPoint((int) x, (int) y));
                    float dst[] = mapPoint(x, y);
                    EpdController.addStrokePoint(strokeWidth, dst[0], dst[1], pressure, size, eventTime);
                } else {
//                    onEraserMoving(x, y);
                    eraserLine(x, y);
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            {
                if (mStatus == STATUS_PEN) {
                    float dst[] = mapPoint(x, y);
                    EpdController.finishStroke(strokeWidth, dst[0], dst[1], pressure, size, eventTime);
                } else {
                    //恢复手写模式
                    if (mHandler != null) {
                        mHandler.removeMessages(MSG_RE_ENTER_SCRIBE);
                        Message msg = mHandler.obtainMessage(MSG_RE_ENTER_SCRIBE);
                        mHandler.sendMessageDelayed(msg, 500);
                    }
                }
                mStatus = STATUS_NONE;
                break;
            }
            default:
                break;
        }
        return true;
    }

    private void eraserLine(float x, float y) {
        List<DrawPoint> line = findDrawLine(x, y);
        if (line != null) {
            mLayerIfs.remove(line);
            EpdController.invalidate(this, UpdateMode.DU);
        }
    }

    private List<DrawPoint> findDrawLine(float x, float y) {
        int padding = ERASER_WIDTH >> 1;
        mEraserRect.set(x - padding, y - padding, x + padding, y + padding);
        for (int i = 0; i < mLayerIfs.size(); i++) {
            List<DrawPoint> pathInfo = mLayerIfs.get(i);
            if (pathInfo != null) {
                for (int j = 0; j < pathInfo.size(); j++) {
                    DrawPoint point = pathInfo.get(j);
                    if (mEraserRect.contains(point.x, point.y)) {
                        return pathInfo;
                    }
                }
            }
        }
        return null;
    }

    private void onEraserMoving(float x, float y) {
        int padding = ERASER_WIDTH >> 1;
        mEraserRect.set(x - padding, y - padding, x + padding, y + padding);
        for (int i = 0; i < mLayerIfs.size(); i++) {
            List<DrawPoint> pathInfo = mLayerIfs.get(i);
            if (pathInfo != null) {
                for (int j = 0; j < pathInfo.size(); j++) {
                    DrawPoint point = pathInfo.get(j);
                    if (mEraserRect.contains(point.x, point.y)) {
                        point.markInvalid();
                    }
                }
            }
        }
        EpdController.invalidate(this, UpdateMode.DU);
    }

    public void reDraw() {
        if (mStatus != STATUS_NONE)
            return;
        EpdController.leaveScribbleMode(this);
        EpdController.invalidate(this, UpdateMode.DU);
        if (mHandler != null) {
            mHandler.removeMessages(MSG_RE_ENTER_SCRIBE);
            Message msg = mHandler.obtainMessage(MSG_RE_ENTER_SCRIBE);
            mHandler.sendMessageDelayed(msg, 500);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mLayerIfs != null) {
            mPath.reset();
            for (int i = 0; i < mLayerIfs.size(); i++) {
                List<DrawPoint> pathInfo = mLayerIfs.get(i);
                if (!pathInfo.isEmpty()) {
                    boolean valid = false;
                    for (int j = 0; j < pathInfo.size(); j++) {
                        DrawPoint point = pathInfo.get(j);
                        if (!point.isValid()) {
                            valid = false;
                            continue;
                        }
                        if (!valid) {
                            mPath.moveTo(point.x, point.y);
                        } else {
                            mPath.lineTo(point.x, point.y);
                        }
                        valid = true;
                    }
                    canvas.drawPath(mPath, mPaint);
                }
            }
        }
    }

    @Override
    public void revert() {
        if (mStatus != STATUS_NONE)
            return;

        if (mLayerIfs != null && !mLayerIfs.isEmpty()) {
            mLayerIfs.pop();
        }
        reDraw();
    }

    @Override
    public void clear() {
        if (mStatus != STATUS_NONE)
            return;

        mLayerIfs.clear();
        reDraw();
    }

    @Override
    public String getPathString() {
        try {
            if (mLayerIfs != null) {
                JSONArray jsonLayers = new JSONArray();
                for (int i = 0; i < mLayerIfs.size(); i++) {
                    List<DrawPoint> layer = mLayerIfs.get(i);
                    JSONArray jsonLayer = new JSONArray();
                    for (int j = 0; j < layer.size(); j++) {
                        Point point = layer.get(j);
                        JSONObject json = new JSONObject();
                        json.put("x", point.x);
                        json.put("y", point.y);
                        jsonLayer.put(json);
                    }
                    jsonLayers.put(jsonLayer);
                }
                return jsonLayers.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    public void setPathString(String pathJson) {
        try {
            JSONArray json = new JSONArray(pathJson);
            Stack<List<DrawPoint>> layers = new Stack<>();
            for (int i = 0; i < json.length(); i++) {
                JSONArray layer = json.getJSONArray(i);
                List<DrawPoint> layerIf = new ArrayList<>();
                for (int j = 0; j < layer.length(); j++) {
                    JSONObject jsonPoint = layer.optJSONObject(j);
                    layerIf.add(new DrawPoint(jsonPoint.optInt("x"), jsonPoint.optInt("y")));
                }
                layers.add(layerIf);
            }
            this.mLayerIfs = layers;
            EpdController.invalidate(this, UpdateMode.DU);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private float[] mapPoint(float x, float y) {
        x = Math.min(Math.max(0, x), getWidth());
        y = Math.min(Math.max(0, y), getHeight());

        final int viewLocation[] = {0, 0};
        getLocationOnScreen(viewLocation);
        final Matrix viewMatrix = new Matrix();
        DeviceConfig deviceConfig = DeviceConfig.sharedInstance(getContext(), "note");
        viewMatrix.postRotate(deviceConfig.getViewPostOrientation());
        viewMatrix.postTranslate(deviceConfig.getViewPostTx(), deviceConfig.getViewPostTy());

        float screenPoints[] = {viewLocation[0] + x, viewLocation[1] + y};
        float dst[] = {0, 0};
        viewMatrix.mapPoints(dst, screenPoints);
        return dst;
    }

    private static class DrawPoint extends Point {
        private boolean mValid = true;
        public DrawPoint(int x, int y) {
            super(x, y);
        }

        public void markInvalid() {
            this.mValid = false;
        }

        public boolean isValid() {
            return mValid;
        }
    }
}
